package chapter02.part05;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.CRC32;

/**
 * 了解内存映射文件
 * 编写一个计算文件的 CRC32 校验和的程序
 * 使用两种方式并比较性能
 * 传统输入和内存映射文件输入
 * 
 * 对于中等大小文件的顺序读入没必要使用内存映射
 * 
 * @author insight
 * @since 2021/8/23
 */
public class MemoryMapTest {
    public static void main(String[] args) throws IOException {
        Path file = Paths.get(
                "E:\\Epic\\Baldurs Gate 3\\Data\\Engine.pak");
        
        // Input Stream: 12699ms
        System.out.print("Input Stream: ");
        long st = System.currentTimeMillis();
        long crc = checkSumInputStream(file);
        long ed = System.currentTimeMillis();
        System.out.println((ed - st) + "ms");
        System.out.println(Long.toHexString(crc));
        
        // Buffered Input Stream: 37ms
        System.out.print("Buffered Input Stream: ");
        st = System.currentTimeMillis();
        crc = checkSumBufferedInputStream(file);
        ed = System.currentTimeMillis();
        System.out.println((ed - st) + "ms");
        System.out.println(Long.toHexString(crc));
        
        // Random Access File: 16477ms
        System.out.print("Random Access File: ");
        st = System.currentTimeMillis();
        crc = checkSumRandomAccessFile(file);
        ed = System.currentTimeMillis();
        System.out.println((ed - st) + "ms");
        System.out.println(Long.toHexString(crc));
        
        // Memory Mapped File: 32ms
        System.out.print("Memory Mapped File: ");
        st = System.currentTimeMillis();
        crc = checkSumMemoryMap(file);
        ed = System.currentTimeMillis();
        System.out.println((ed - st) + "ms");
        System.out.println(Long.toHexString(crc));
    }
    
    /**
     * 最简单的文件IO
     */
    public static long checkSumInputStream(Path filename) throws IOException {
        try (InputStream in = Files.newInputStream(filename)) {
            CRC32 crc32 = new CRC32();
            int c;
            
            while ((c = in.read()) != -1) crc32.update(c);

            return crc32.getValue();
        }
    }

    /**
     * 使用缓冲区IO
     */
    public static long checkSumBufferedInputStream(Path filename)
            throws IOException {
        try (BufferedInputStream in =
                     new BufferedInputStream(Files.newInputStream(filename))) {
            CRC32 crc32 = new CRC32();
            int c;

            while ((c = in.read()) != -1) crc32.update(c);

            return crc32.getValue();
        }
    }

    /**
     * 使用随机访问IO
     */
    public static long checkSumRandomAccessFile(Path filename)
            throws IOException {
        try (RandomAccessFile in = new RandomAccessFile(filename.toFile(), "r")) {
            long length = in.length();
            CRC32 crc32 = new CRC32();

            for (long i = 0; i < length; i++) {
                in.seek(i);
                int c = in.readByte();
                crc32.update(c);
            }

            return crc32.getValue();
        }
    }

    /**
     * 使用内存映射IO
     */
    public static long checkSumMemoryMap(Path filename)
            throws IOException {
        /*
         * 1. 获取通道
         * 2. 调用 map 获取 buffer
         * 3. get put 相关方法访问数据
         */
        try (FileChannel in = FileChannel.open(filename)) {
            long length = in.size();
            MappedByteBuffer buffer = in.map(
                    FileChannel.MapMode.READ_ONLY, 0, length);
            CRC32 crc32 = new CRC32();

            for (int i = 0; i < length; i++) {
                int c = buffer.get(i);
                crc32.update(c);
            }

            return crc32.getValue();
        }
    }
}